/*******************************************************************************
 * Copyleft (c) 2021 Kcode
 *
 * @file    v4l2.c
 * @brief   v4l2设备的文件，参考luvcview
 * @author  K
 * @version 0.0.1
 * @date    2021-07-26
 * @license MulanPSL-1.0
 *
 * 文件修改历史：
 * <时间>       | <版本>    | <作者>  | <描述>
 * 2021-07-26   | v0.0.1    | Kcode   | v4l2设备的文件
 * -----------------------------------------------------------------------------
 ******************************************************************************/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include <sys/mman.h>

#include "config.h"
#include "debug_manager.h"
#include "video_manager.h"
#include "disp_manager.h"

static T_VIDEOOPR s_V4l2VideoOpr;

static int V4l2GetFrameForReadWrite(PT_VIDEODEVICE video_device,
                                            PT_VIDEOBUF video_buf);

static int V4l2PutFrameForReadWrite(PT_VIDEODEVICE video_device,
                                            PT_VIDEOBUF video_buf);


static int V4l2PutFrameForStreaming(PT_VIDEODEVICE video_device,
                                            PT_VIDEOBUF video_buf);

static int V4l2GetFrameForStreaming(PT_VIDEODEVICE video_device,
                                            PT_VIDEOBUF video_buf);


/* 所支持的格式 */
static int s_isSupportedFormat[] = {
    V4L2_PIX_FMT_YUYV,
    V4L2_PIX_FMT_MJPEG,
    V4L2_PIX_FMT_RGB565,
};

/*!
 * @brief  判断是否支持此格式
 * @return  0：不支持 1：支持
 */
static int isSupportThisFormat(int pixel_format)
{
    int i;
    int size;

    size = sizeof(s_isSupportedFormat)/sizeof(s_isSupportedFormat[0]);
    for(i = 0; i < size; i++)
    {
        if (s_isSupportedFormat[i] == pixel_format)
            return 1;
    }

    return 0;
}
    
/*!
 * @brief 初始化设备
 *        1、确定是否为视频捕获设备、支持何种接口(streaming/read、write)
 *        2、查询支持何种属性
 *        3、设置摄像头格式
 *        4、向驱动程序申请buffer
 *        5、确定每个buffer的信息且mmap
 *        6、把buffer放入队列
 *        7、启动设备
 *        8、poll机制等待数据
 *        9、有数据则从队列中取出并处理
 *       10、再次放入队列，重复步骤
 *       11、不操作时停止设备
 */
static int V4l2InitDevice(char *dev_name, PT_VIDEODEVICE video_device)
{
    int i;
    int fd;
    int ret;
    int lcd_width;
    int lcd_height;
    int lcd_bpp;
    struct v4l2_capability v4l2cap;
    struct v4l2_fmtdesc v4l2fmt_desc;
    struct v4l2_format v4l2fmt;
    struct v4l2_requestbuffers v4l2req_buf;
    struct v4l2_buffer v4l2buf;
        
    /*!
     * 打开设备
     */
    fd = open(dev_name, O_RDWR);
    if (fd < 0)
    {
        DebugPrint(APP_ERR"open error! Function:%s Line:%d\n", 
                                    __FUNCTION__, __LINE__);
        return -1;
    }

    video_device->fd = fd;
    
    /*！
     * 调用VIDIOC_QUERYCAP的ioctl
     */
    memset(&v4l2cap, 0, sizeof(struct v4l2_capability));
    ret = ioctl(fd, VIDIOC_QUERYCAP, &v4l2cap);
    if (ret < 0)
    {
        DebugPrint(APP_ERR"Error opening device %s: unable to query device.\n",
           dev_name);
        goto err_eixt;
    }

    /*!
     * 判断是否为视频捕获设备
     */
    if (!(v4l2cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
    {
        DebugPrint(APP_ERR"%s is not a video capture device\n", dev_name);
        goto err_eixt;
    }

    if (v4l2cap.capabilities & V4L2_CAP_STREAMING)
	    DebugPrint(APP_NOTICE"%s supports streaming i/o\n", dev_name);
  
	if (v4l2cap.capabilities & V4L2_CAP_READWRITE)
	    DebugPrint(APP_NOTICE"%s supports read i/o\n", dev_name);
    
    /*!
     * 枚举所支持的格式
     */
    memset(&v4l2fmt_desc, 0, sizeof(v4l2fmt_desc));
    v4l2fmt_desc.index = 0;
    v4l2fmt_desc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    while ((ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4l2fmt_desc)) == 0)
    {
        if(isSupportThisFormat(v4l2fmt_desc.pixelformat))
        {
            video_device->pixel_format = v4l2fmt_desc.pixelformat;
            break;
        }    
    	v4l2fmt_desc.index++;
    }

    /*!
     * 无支持的设备格式
     */
    if (!video_device->pixel_format)
    {
        DebugPrint(APP_ERR"Can not support the format of this device\n");
        goto err_eixt;
    }

    /*!
     * 设置格式
     */
    GetDispResolution(&lcd_width, &lcd_height, &lcd_bpp);
    memset(&v4l2fmt, 0, sizeof(struct v4l2_format));
    v4l2fmt.type            = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2fmt.fmt.pix.width       = lcd_width;
    v4l2fmt.fmt.pix.height      = lcd_height;
    v4l2fmt.fmt.pix.pixelformat = video_device->pixel_format;
    v4l2fmt.fmt.pix.field       = V4L2_FIELD_ANY;

    /* 如果驱动参数无法支持某些参数(分辨率)，会调整这些参数，并返回给应用程序 */
    ret = ioctl(fd, VIDIOC_S_FMT, &v4l2fmt);
    if (ret < 0)
    {
	    DebugPrint(APP_ERR"Unable to set format: %d Function:%s Line:%d\n", 
                                    __FUNCTION__, __LINE__);
	    goto err_eixt;
    }

    /* 重新记录分辨率 */
    video_device->width  = v4l2fmt.fmt.pix.width;
    video_device->height = v4l2fmt.fmt.pix.height;

    /*!
     * 申请buffer
     */
    memset(&v4l2req_buf, 0, sizeof(struct v4l2_requestbuffers));
    v4l2req_buf.count  = REQ_BUFFER_NUM;
    v4l2req_buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2req_buf.memory = V4L2_MEMORY_MMAP;

    ret = ioctl(fd, VIDIOC_REQBUFS, &v4l2req_buf);
    if (ret < 0)
    {
	    DebugPrint(APP_ERR"Unable to allocate buffers Function:%s Line:%d\n", 
                                    __FUNCTION__, __LINE__);
	    goto err_eixt;
    }

    /* 重新记录缓冲区个数 */
    video_device->videobuf_num = v4l2req_buf.count;

    /*!
     * 对于streaming接口的设备，需要查询缓冲区信息且mmap缓冲区
     * 对于read/write接口的设置，只需分配内存，调用read
     */
    if (v4l2cap.capabilities & V4L2_CAP_STREAMING)
    {
        
        /* 查询并映射每一个缓冲区 */
        for (i = 0; i < video_device->videobuf_num; i++)
        {
            /* 设置 */
        	memset(&v4l2buf, 0, sizeof(struct v4l2_buffer));
        	v4l2buf.index  = i;
        	v4l2buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        	v4l2buf.memory = V4L2_MEMORY_MMAP;

            /* 查询 */
        	ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2buf);
        	if (ret < 0)
            {
        	    DebugPrint(APP_ERR"Unable to query buffer\n");
        	    goto err_eixt;
        	}

            /* mmap */
            video_device->videeobuf_maxlen = v4l2buf.length;
            video_device->videobuf[i] = mmap(0 /* start anywhere */ ,
                v4l2buf.length, PROT_READ, MAP_SHARED, fd, v4l2buf.m.offset);
            if (video_device->videobuf[i] == MAP_FAILED)
            {
                DebugPrint(APP_ERR"Unable to map buffer\n");
                goto err_eixt;
            }
        }

        /*!
         * 缓冲区逐个放入队列
         */
        for (i = 0; i < video_device->videobuf_num; ++i)
        {
            /* 设置 */
            memset(&v4l2buf, 0, sizeof(struct v4l2_buffer));
            v4l2buf.index  = i;
            v4l2buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            v4l2buf.memory = V4L2_MEMORY_MMAP;

            /* 放入队列 */
            ret = ioctl(fd, VIDIOC_QBUF, &v4l2buf);
            if (ret < 0)
            {
                DebugPrint(APP_ERR"Unable to queue buffer\n");
                goto err_eixt;
            }
        }
    }
    else if (v4l2cap.capabilities & V4L2_CAP_READWRITE)
    {
        s_V4l2VideoOpr.GetFrame = V4l2GetFrameForReadWrite;
        s_V4l2VideoOpr.PutFrame = V4l2PutFrameForReadWrite;
        
        video_device->videobuf_num    = 1;

        /* 在程序所能支持的格式中，一个像素最多占4字节 */
        video_device->videeobuf_maxlen = video_device->width * \
            video_device->height * 4;
        
        video_device->videobuf[0] = (unsigned char *)malloc(
                        video_device->videeobuf_maxlen);
    }

    video_device->video_opr = &s_V4l2VideoOpr;

    return 0;
    
err_eixt:
    close(fd);
    return -1;
}

/*!
 * @brief  退出设备
 * @return  0
 */
static int V4l2ExitDevice(PT_VIDEODEVICE video_device)
{
    int i;

    if (s_V4l2VideoOpr.GetFrame == V4l2GetFrameForStreaming)
    {
        for(i = 0; i < video_device->videobuf_num; i++)
        {
            if (video_device->videobuf[i])
            {
                munmap(video_device->videobuf[i], video_device->videeobuf_maxlen);
                video_device->videobuf[i] = NULL;
            }
        }
    }
    else if (s_V4l2VideoOpr.GetFrame == V4l2GetFrameForReadWrite)
        free(video_device->videobuf[0]);

    close(video_device->fd);
    
    return 0;
}

/*!
 * @brief  启动设备
 * @return  0：成功 -1：失败
 */
static int V4l2StartDevice(PT_VIDEODEVICE video_device)
{
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int ret;

    ret = ioctl(video_device->fd, VIDIOC_STREAMON, &type);
    if (ret < 0)
    {
        DebugPrint(APP_ERR"Unable to start capture\n");
        return -1;
    }

    return 0;
}

/*!
 * @brief  停止设备
 * @return  0：成功 -1：失败
 */
static int V4l2StopDevice(PT_VIDEODEVICE video_device)
{
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int ret;

    ret = ioctl(video_device->fd, VIDIOC_STREAMOFF, &type);
    if (ret < 0)
    {
        DebugPrint(APP_ERR"Unable to stop capture\n");
        return -1;
    }
    
    return 0;
}

/*!
 * @brief  获得视频数据，Streaming接口
 *         poll，VIDIOC_DQBUF
 * @return  0：成功 -1：失败
 */
static int V4l2GetFrameForStreaming(PT_VIDEODEVICE video_device,
                                            PT_VIDEOBUF video_buf)
{
    int ret;
    struct pollfd poll_fd[1];
    struct v4l2_buffer v4l2buf;

    /*!
     * 调用poll机制
     */
    poll_fd[0].fd     = video_device->fd;
    poll_fd[0].events = POLLIN;

    /* 永远等待 */
    ret = poll(poll_fd, 1, -1);
    if (ret <= 0)
    {
        DebugPrint(APP_ERR"Poll error!\n");
        return -1;
    }
    else
    {   
        /*!
         * VIDIOC_DQBUF，从队列中取出缓冲区
         */
        memset(&v4l2buf, 0, sizeof(struct v4l2_buffer));
        v4l2buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        v4l2buf.memory = V4L2_MEMORY_MMAP;
        
        ret = ioctl(video_device->fd, VIDIOC_DQBUF, &v4l2buf);
        if (ret < 0)
        {
            DebugPrint(APP_ERR"Unable to dequeue buffer\n");
            return -1;
        }

        /* 记录取出缓冲区的index */
        video_device->cur_bufindex = v4l2buf.index;

        /* 设置传入的buf */
        video_buf->pixel_format       = video_device->pixel_format;
        video_buf->pixel_data.width   = video_device->width;
        video_buf->pixel_data.height  = video_device->height;
        video_buf->pixel_data.bpp  = (video_device->pixel_format == V4L2_PIX_FMT_YUYV)? 16 :\
                                     (video_device->pixel_format == V4L2_PIX_FMT_MJPEG)? 0 :\
                                     (video_device->pixel_format == V4L2_PIX_FMT_RGB565)? 16 : 0;
        video_buf->pixel_data.linebytes  = video_device->width  * video_buf->pixel_data.bpp / 8;
        video_buf->pixel_data.TotalBytes = v4l2buf.bytesused;
        video_buf->pixel_data.PixelDatas = video_device->videobuf[v4l2buf.index];


    }
    
    return 0;
}

/*!
 * @brief  获得视频数据，read/write接口
 *         VIDIOC_REQBUF
 * @return  0：成功 -1：失败
 */
static int V4l2GetFrameForReadWrite(PT_VIDEODEVICE video_device,
                                            PT_VIDEOBUF video_buf)
{
    int ret;
    
    ret = read(video_device->fd, video_device->videobuf[0],
                               video_device->videeobuf_maxlen);
    if (ret <= 0)
       return -1;

    /* 设置传入的buf */
    video_buf->pixel_format       = video_device->pixel_format;
    video_buf->pixel_data.width   = video_device->width;
    video_buf->pixel_data.height  = video_device->height;
    video_buf->pixel_data.bpp  = (video_device->pixel_format == V4L2_PIX_FMT_YUYV)? 16 :\
                                (video_device->pixel_format == V4L2_PIX_FMT_MJPEG)? 0 :\
                                (video_device->pixel_format == V4L2_PIX_FMT_RGB565)? 16 : 0;
    video_buf->pixel_data.linebytes  = video_device->width  * video_buf->pixel_data.bpp / 8;
    video_buf->pixel_data.TotalBytes = ret;
    video_buf->pixel_data.PixelDatas = video_device->videobuf[0];
           
    return 0;
}
                                            
/*!
 * @brief  把视频数据重新放入队列，Streaming接口，VIDIOC_QBUF
 * @return  0：成功 -1：失败
 */
static int V4l2PutFrameForStreaming(PT_VIDEODEVICE video_device,
                                            PT_VIDEOBUF video_buf)
{
    int ret;
    struct v4l2_buffer v4l2buf;
    
    memset(&v4l2buf, 0, sizeof(struct v4l2_buffer));
    v4l2buf.index  = video_device->cur_bufindex;
    v4l2buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2buf.memory = V4L2_MEMORY_MMAP;
    
    ret = ioctl(video_device->fd, VIDIOC_QBUF, &v4l2buf);
    if (ret < 0)
    {
        DebugPrint(APP_ERR"Unable to queue buffer\n");
        return -1;
    }

    return 0;
}

/*!
 * @brief  获得视频数据的原始格式
 * @return  0：成功 -1：失败
 */
static int V4l2GetFormat(PT_VIDEODEVICE video_device)
{
    return video_device->pixel_format;
}

/*!
 * @brief  把视频数据重新放入，read/write接口，无操作
 * @return  0：成功 -1：失败
 */
static int V4l2PutFrameForReadWrite(PT_VIDEODEVICE video_device,
                                            PT_VIDEOBUF video_buf)
{
    return 0;
}

/*!
 * 构造T_VIDEOOPR结构体
 */
static T_VIDEOOPR s_V4l2VideoOpr = {
    .name         = "v4l2",
    .InitDevice   = V4l2InitDevice,
    .ExitDevice   = V4l2ExitDevice,
    .StartDevice  = V4l2StartDevice,
    .StopDevice   = V4l2StopDevice,
    .GetFrame     = V4l2GetFrameForStreaming,
    .GetFormat    = V4l2GetFormat,
    .PutFrame     = V4l2PutFrameForStreaming,        
};

/*!
 * @brief  初始化函数，把s_V412VideoOpr结构体注册进链表中
 * @param  [in] 无
 * @return 0：成功 -1：失败 
 */
int V4l2Init(void)
{
    return RegisterVideoOpr(&s_V4l2VideoOpr);
}
